
// THIS FILE DOES NOT COMPILE ALONE
// this file is included in loader.h (because it contains template definitions)

#include <windows.h>
#include "base/material.h"
#include "base/triangle.h"
#include "base/tv_iface.h"

#include <assert.h>

#include <fstream>
#include <sstream>

// todo
#include <iostream>
using std::cout;
using std::endl;
using std::flush;


template<class vtype, class cvtype, class co_type, class comp_type, class it_type>
const char* loader<vtype,cvtype,co_type,comp_type,it_type> :: 
    MATERIAL_MODES[] = 
{
    "VCOL_PAINT",
};


template<class vtype, class cvtype, class co_type, class comp_type, class it_type>
const int loader<vtype,cvtype,co_type,comp_type,it_type> :: 
    MAT_MODES_COUNT = 1;


template<class vtype, class cvtype, class co_type, class comp_type, class it_type>
error loader<vtype,cvtype,co_type,comp_type,it_type> :: 
    load_tgt(const char* path, 
             object3d<vtype,cvtype,co_type,comp_type,it_type> & ob,
             bool clear_3dobject)
{
    std::istringstream ss;
    char line[LINE_LEN];
    int act_line = 1;
    std::ifstream fin;
    error e;
    int vcount;
    int cvcount;
    int tcount;
    int ctcount;
    int mcount;
    int vi;
    int voffset;
    int cvoffset;
    int moffset;

    fin.open(path);
    if(!fin) {
        return error(error::ERROR_couldnot_open_file,-1);
    }

    //////////
    //////////      V E R T I C E S
    //////////

    // The first line should be "vertices", 
    // but we don't care; it just needs to be a line.
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Number of non-coloured vertices.
    fin >> vcount;

    if( ! fin ) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }
    if(vcount < 0) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }
    else if(vcount > MAX_VERTS) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }

    // The rest of this line is ignored (it should be empty).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Now comes the vertex-syntax explanation
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }
    
    // clear the object's data if the user wants that
    if(clear_3dobject)
        ob.clear();
    
    // Just in time... vertices!

    assert(vcount >= 0 && vcount<=MAX_VERTS);
    
    voffset = ob.verts.size();
    vi = ob.verts.size();
    ob.verts.resize(ob.verts.size() + vcount);

    for(int loop=0; loop<vcount; loop++)
    {
        act_line++;
        fin >> ob.verts[vi].x;
        fin >> ob.verts[vi].y;
        fin >> ob.verts[vi].z;

        fin >> ob.verts[vi].normal.x;
        fin >> ob.verts[vi].normal.y;
        fin >> ob.verts[vi].normal.z;

        if( ! fin ) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(ob.verts[vi].x < ob.xbord[0]) // x bottom boundary
            ob.xbord[0] = ob.verts[vi].x; 
        if(ob.verts[vi].x > ob.xbord[1]) // x top boundary
            ob.xbord[1] = ob.verts[vi].x;
        if(ob.verts[vi].y < ob.ybord[0]) // y bottom boundary
            ob.ybord[0] = ob.verts[vi].y; 
        if(ob.verts[vi].y > ob.ybord[1]) // y top boundary
            ob.ybord[1] = ob.verts[vi].y;
        if(ob.verts[vi].z < ob.zbord[0]) // z bottom boundary
            ob.zbord[0] = ob.verts[vi].z; 
        if(ob.verts[vi].z > ob.zbord[1]) // z top boundary
            ob.zbord[1] = ob.verts[vi].z;

        fin.getline(line, LINE_LEN); // the rest of the line is ignored
        if(fin.bad()) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(fin.fail()) {
            fin.close();
            return error(error::ERROR_file_line_too_long, act_line);
        }

        vi++;
    }

    ob.vcount += vcount;

    act_line++;
    // Empty seperator line.
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    
    // //  //  // 
    // //  //  //   C O L O U R E D     V E R T I C E S
    // //  //  // 

    // The header line should be "coloured vertices", 
    // but again we do not care (we expect any line).
    act_line++;
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Number of coloured vertices.
    fin >> cvcount;

    if( ! fin ) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }
    if(cvcount < 0) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }
    else if(cvcount > MAX_CVERTS) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }

    // The rest of this line is ignored (it should be empty).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // The coloured vertex syntax explanation
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    assert(cvcount >= 0 && cvcount<=MAX_VERTS);
    
    cvoffset = ob.cverts.size();
    vi = ob.cverts.size();
    ob.cverts.resize(ob.cverts.size() + cvcount);

    for(int loop=0; loop<cvcount; loop++)
    {
        act_line++;
        fin >> ob.cverts[vi].x;
        fin >> ob.cverts[vi].y;
        fin >> ob.cverts[vi].z;

        fin >> ob.cverts[vi].normal.x;
        fin >> ob.cverts[vi].normal.y;
        fin >> ob.cverts[vi].normal.z;

        fin >> ob.cverts[vi].col[0];
        fin >> ob.cverts[vi].col[1];
        fin >> ob.cverts[vi].col[2];
        fin >> ob.cverts[vi].col[3];

        if( ! fin ) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(ob.cverts[vi].x < ob.xbord[0]) // x bottom boundary
            ob.xbord[0] = ob.cverts[vi].x; 
        if(ob.cverts[vi].x > ob.xbord[1]) // x top boundary
            ob.xbord[1] = ob.cverts[vi].x;
        if(ob.cverts[vi].y < ob.ybord[0]) // y bottom boundary
            ob.ybord[0] = ob.cverts[vi].y; 
        if(ob.cverts[vi].y > ob.ybord[1]) // y top boundary
            ob.ybord[1] = ob.cverts[vi].y;
        if(ob.cverts[vi].z < ob.zbord[0]) // z bottom boundary
            ob.zbord[0] = ob.cverts[vi].z; 
        if(ob.cverts[vi].z > ob.zbord[1]) // z top boundary
            ob.zbord[1] = ob.cverts[vi].z;

        fin.getline(line, LINE_LEN); // the rest of the line is ignored
        if(fin.bad()) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(fin.fail()) {
            fin.close();
            return error(error::ERROR_file_line_too_long, act_line);
        }

        vi++;
    }
    
    ob.cvcount += cvcount;

    act_line++;
    // Empty seperator line.
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    //  ///////     M               R               S
    ////                A       E       I       L
    //  ///////             T               A

    // A header saying "materials" should go first,
    // but as always we do not care.
    act_line++;
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Number of materials.
    fin >> mcount;

    if( ! fin ) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }
    if(mcount < 0) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }
    else if(mcount > MAX_MATERIALS) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }

    // The rest of this line is ignored (it should be empty).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Now comes the material-syntax explanation
    // (and again in fact we do not read it).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    assert(mcount >= 0 && mcount<=MAX_MATERIALS);
    
    moffset = ob.mats.size();
    vi = ob.mats.size();
    ob.mats.resize(ob.mats.size() + mcount);

    for(int loop=0; loop<mcount; loop++)
    {
        act_line++;

        fin >> ob.mats[vi].diffuse[0];
        fin >> ob.mats[vi].diffuse[1];
        fin >> ob.mats[vi].diffuse[2];

        fin >> ob.mats[vi].specular[0];
        fin >> ob.mats[vi].specular[1];
        fin >> ob.mats[vi].specular[2];

        fin >> ob.mats[vi].shininess;

        if( ! fin ) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        fin.getline(line, LINE_LEN); 
        if(fin.bad()) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(fin.fail()) {
            fin.close();
            return error(error::ERROR_file_line_too_long, act_line);
        }

        if(fin.gcount() > 1)   // it has been read more than '\n' 
        {
            // gather material's modes, if any
            std::string option;
            ss.clear();
            ss.str(std::string(line));

            ss >> option;

            if(ss.bad()) {
                fin.close();
                return error(error::ERROR_internal_istringstream, act_line);
            }
            else if(ss.fail()==false) {
                if(option == MATERIAL_MODES[0]) { // vertex colour paint
                    ob.mats[vi].vcol_paint = true;
                }
            }
        }

        vi++;
    }

    
    ob.mcount += mcount;


    act_line++;
    // Empty seperator line.
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    ////////    ///
    //  //////////      N O N - C O L O U R E D     T R I A N G L E S
    /////   /////

    // The heading line should say "triangles",
    // but as always, we do not what does it actually say.
    act_line++;
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Number of non-coloured triangles.
    fin >> tcount;

    if( ! fin ) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }
    if(tcount < 0) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }
    else if(tcount > MAX_TRIANGLES) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }

    // The rest of this line is ignored (it should be empty).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Now comes the non-coloured triangle syntax explanation
    // (and again we in fact do not read it).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    assert(tcount >= 0 && tcount<=MAX_TRIANGLES);
    

    vi = ob.tris.size();
    ob.tris.resize(ob.tris.size() + tcount);

    for(int loop=0; loop<tcount; loop++)
    {
        act_line++;
        fin >> ob.tris[vi].verts[0];
        fin >> ob.tris[vi].verts[1];
        fin >> ob.tris[vi].verts[2];

        ob.tris[vi].verts[0] += voffset;
        ob.tris[vi].verts[1] += voffset;
        ob.tris[vi].verts[2] += voffset;

        if( ! fin ) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if( ob.tris[vi].verts[0] < 0 || ob.tris[vi].verts[0] >= ob.vcount ||
            ob.tris[vi].verts[1] < 0 || ob.tris[vi].verts[1] >= ob.vcount ||
            ob.tris[vi].verts[2] < 0 || ob.tris[vi].verts[2] >= ob.vcount ) {
            // the triangle contains non existing vertex
            fin.close();
            return error(error::ERROR_bad_vertex_in_triangle, act_line);
        }

        fin.getline(line, LINE_LEN); // the rest of the line is ignored
        if(fin.bad()) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(fin.fail()) {
            fin.close();
            return error(error::ERROR_file_line_too_long, act_line);
        }

        if(fin.gcount() > 1) { // more than '\n' encountered
            ss.clear();
            ss.str(std::string(line));

            ss >> ob.tris[vi].mat;

            if(ss.bad()) {
                fin.close();
                return error(error::ERROR_internal_istringstream, act_line);
            }
            else if(ss.fail()) 
                ob.tris[vi].mat = -1; // this triangle has no material
            else {
                ob.tris[vi].mat += moffset;
                if(ob.tris[vi].mat < 0 || ob.tris[vi].mat >= ob.mcount) {
                    // the triangle contains non existing material
                    fin.close();
                    return error(error::ERROR_bad_material_in_triangle, act_line);
                }
            }
        }

        vi++;
    }

    
    ob.tcount = tcount;


    act_line++;
    // Empty seperator line.
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }
    
    /////   ///////
    //  ///     //      C O L O U R E D       T R I A N G L E S
    ////   //////

    act_line++;
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Number of coloured triangles.
    fin >> ctcount;

    if( ! fin ) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }
    if(ctcount < 0) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }
    else if(ctcount > MAX_CTRIANGLES) {
        fin.close();
        // todo: add another error description
        return error(error::ERROR_wrong_data, act_line);
    }

    // The rest of this line is ignored (it should be empty).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    act_line++;
    // Now comes the coloured triangles syntax explanation
    // (and again we in fact do not read it).
    fin.getline(line, LINE_LEN);
    if(fin.bad()) {
        e = file_error(fin, act_line);
        fin.close();
        return e;
    }

    if(fin.fail()) {
        fin.close();
        return error(error::ERROR_file_line_too_long, act_line);
    }

    assert(ctcount >= 0 && ctcount<=MAX_CTRIANGLES);
    

    vi = ob.ctris.size();
    ob.ctris.resize(ob.ctris.size() + ctcount);

    for(int loop=0; loop<ctcount; loop++)
    {
        act_line++;
        fin >> ob.ctris[vi].verts[0];
        fin >> ob.ctris[vi].verts[1];
        fin >> ob.ctris[vi].verts[2];

        ob.ctris[vi].verts[0] += voffset;
        ob.ctris[vi].verts[1] += voffset;
        ob.ctris[vi].verts[2] += voffset;

        if( ! fin ) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if( ob.ctris[vi].verts[0] < 0 || ob.ctris[vi].verts[0] >= ob.cvcount ||
            ob.ctris[vi].verts[1] < 0 || ob.ctris[vi].verts[1] >= ob.cvcount ||
            ob.ctris[vi].verts[2] < 0 || ob.ctris[vi].verts[2] >= ob.cvcount ) {
            // the triangle contains non existing vertex
            fin.close();
            return error(error::ERROR_bad_vertex_in_triangle, act_line);
        }

        fin.getline(line, LINE_LEN); // the rest of the line is ignored
        if(fin.bad()) {
            e = file_error(fin, act_line);
            fin.close();
            return e;
        }

        if(fin.fail()) {
            fin.close();
            return error(error::ERROR_file_line_too_long, act_line);
        }

        if(fin.gcount() > 1) { // more than '\n' encountered
            ss.clear();
            ss.str(std::string(line));

            ss >> ob.ctris[vi].mat;

            if(ss.bad()) {
                fin.close();
                return error(error::ERROR_internal_istringstream, act_line);
            }
            else if(ss.fail()) 
                ob.ctris[vi].mat = -1; // this triangle has no material
            else {
                ob.ctris[vi].mat += moffset;
                if(ob.ctris[vi].mat < 0 || ob.ctris[vi].mat >= ob.mcount) {
                    // the triangle contains non existing material
                    fin.close();
                    return error(error::ERROR_bad_material_in_triangle, act_line);
                }
            }
        }

        vi++;
    }

    
    ob.ctcount = ctcount;

    fin.close();

    return error(error::OK, act_line);
}

template<class vtype, class cvtype, class co_type, class comp_type, class it_type>
error loader<vtype,cvtype,co_type,comp_type,it_type> ::
    load_ply(char* path, jeff::Mesh** mesh, bool clear_mesh)
{
    jeff::Mesh* me;
    error e;
    me = new jeff::Mesh(path);
    if(me->getNumVerts()>0 && me->getNumTriangles()>0) 
    {
        if(clear_mesh && *mesh!=NULL)
            delete *mesh;
        *mesh=me;
        e=error(error::OK); // we do not say anything about the line number
    }
    else
    {
        delete me;
        e=error(error::ERROR_ply_failed);
    }
    return e;
}


template<class vtype, class cvtype, class co_type, class comp_type, class it_type>
error loader<vtype,cvtype,co_type,comp_type,it_type> :: 
    file_error(const std::istream & fin, int line_nr)
{
    if(fin.eof())
        return error(error::ERROR_EOF, line_nr);
    else if(fin.bad())
        return error(error::ERROR_file_IO, line_nr);
    else if(fin.fail())
        return error(error::ERROR_wrong_data, line_nr);
    else
        return error(error::OK, line_nr);
}

